/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002-2006 * Sleepycat Software. All rights reserved. * * $Id: TxnManager.java,v 1.1 2006/05/06 08:59:10 ckaestne Exp $ */ package com.sleepycat.je.txn; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.transaction.xa.Xid; import com.sleepycat.je.DatabaseException; import com.sleepycat.je.LockStats; import com.sleepycat.je.StatsConfig; import com.sleepycat.je.Transaction; import com.sleepycat.je.TransactionConfig; import com.sleepycat.je.TransactionStats; import com.sleepycat.je.dbi.EnvironmentImpl; import com.sleepycat.je.dbi.MemoryBudget; import com.sleepycat.je.latch.Latch; import com.sleepycat.je.latch.LatchSupport; import com.sleepycat.je.utilint.DbLsn; /** * Class to manage transactions. Basically a Set of all transactions with add * and remove methods and a latch around the set. */ public class TxnManager { /* * All NullTxns share the same id so as not to eat from the id number * space. */ static final long NULL_TXN_ID = -1; private static final String DEBUG_NAME = TxnManager.class.getName(); private LockManager lockManager; private EnvironmentImpl env; private Latch allTxnLatch; private Set allTxns; /* Maps Xids to Txns. */ private Map allXATxns; /* Maps Threads to Txns when there are thread implied transactions. */ private Map thread2Txn; private long lastUsedTxnId; private int nActiveSerializable; /* Locker Stats */ private int numCommits; private int numAborts; private int numXAPrepares; private int numXACommits; private int numXAAborts; public TxnManager(EnvironmentImpl env) throws DatabaseException { if (EnvironmentImpl.getFairLatches()) { lockManager = new LatchedLockManager(env); } else { if (env.isNoLocking()) { lockManager = new DummyLockManager(env); } else { lockManager = new SyncedLockManager(env); } } this.env = env; allTxns = new HashSet(); allTxnLatch = LatchSupport.makeLatch(DEBUG_NAME, env); allXATxns = Collections.synchronizedMap(new HashMap()); thread2Txn = Collections.synchronizedMap(new HashMap()); numCommits = 0; numAborts = 0; numXAPrepares = 0; numXACommits = 0; numXAAborts = 0; lastUsedTxnId = 0; } /** * Set the txn id sequence. */ synchronized public void setLastTxnId(long lastId) { this.lastUsedTxnId = lastId; } /** * Get the last used id, for checkpoint info. */ public synchronized long getLastTxnId() { return lastUsedTxnId; } /** * Get the next transaction id to use. */ synchronized long incTxnId() { return ++lastUsedTxnId; } /** * Create a new transaction. * @param parent for nested transactions, not yet supported * @param txnConfig specifies txn attributes * @return the new txn */ public Txn txnBegin(Transaction parent, TransactionConfig txnConfig) throws DatabaseException { if (parent != null) { throw new DatabaseException ("Nested transactions are not supported yet."); } return new Txn(env, txnConfig); } /** * Give transactions and environment access to lock manager. */ public LockManager getLockManager() { return lockManager; } /** * Called when txn is created. */ void registerTxn(Txn txn) throws DatabaseException { allTxnLatch.acquire(); allTxns.add(txn); if (txn.isSerializableIsolation()) { nActiveSerializable++; } allTxnLatch.release(); } /** * Called when txn ends. */ void unRegisterTxn(Txn txn, boolean isCommit) throws DatabaseException { allTxnLatch.acquire(); try { allTxns.remove(txn); /* Remove any accumulated MemoryBudget delta for the Txn. */ env.getMemoryBudget(). updateMiscMemoryUsage(txn.getAccumulatedDelta() - txn.getInMemorySize()); if (isCommit) { numCommits++; } else { numAborts++; } if (txn.isSerializableIsolation()) { nActiveSerializable--; } } finally { allTxnLatch.release(); } } /** * Called when txn is created. */ public void registerXATxn(Xid xid, Txn txn, boolean isPrepare) throws DatabaseException { if (!allXATxns.containsKey(xid)) { allXATxns.put(xid, txn); env.getMemoryBudget().updateMiscMemoryUsage (MemoryBudget.HASHMAP_ENTRY_OVERHEAD); } if (isPrepare) { numXAPrepares++; } } /** * Called when txn ends. */ void unRegisterXATxn(Xid xid, boolean isCommit) throws DatabaseException { if (allXATxns.remove(xid) == null) { throw new DatabaseException ("XA Transaction " + xid + " can not be unregistered."); } env.getMemoryBudget().updateMiscMemoryUsage (0 - MemoryBudget.HASHMAP_ENTRY_OVERHEAD); if (isCommit) { numXACommits++; } else { numXAAborts++; } } /** * Retrieve a Txn object from an Xid. */ public Txn getTxnFromXid(Xid xid) throws DatabaseException { return (Txn) allXATxns.get(xid); } /** * Called when txn is assoc'd with this thread. */ public void setTxnForThread(Transaction txn) { Thread curThread = Thread.currentThread(); thread2Txn.put(curThread, txn); } /** * Called when txn is assoc'd with this thread. */ public Transaction unsetTxnForThread() throws DatabaseException { Thread curThread = Thread.currentThread(); return (Transaction) thread2Txn.remove(curThread); } /** * Retrieve a Txn object for this Thread. */ public Transaction getTxnForThread() throws DatabaseException { return (Transaction) thread2Txn.get(Thread.currentThread()); } public Xid[] XARecover() throws DatabaseException { Set xidSet = allXATxns.keySet(); Xid[] ret = new Xid[xidSet.size()]; ret = (Xid[]) xidSet.toArray(ret); return ret; } /** * Returns whether there are any active serializable transactions, * excluding the transaction given (if non-null). This is intentionally * returned without latching, since latching would not make the act of * reading an integer more atomic than it already is. */ public boolean areOtherSerializableTransactionsActive(Locker excludeLocker) { int exclude = (excludeLocker != null && excludeLocker.isSerializableIsolation()) ? 1 : 0; return (nActiveSerializable - exclude > 0); } /** * Get the earliest LSN of all the active transactions, for checkpoint. */ public long getFirstActiveLsn() throws DatabaseException { /* * Note that the latching hierarchy calls for getting allTxnLatch * first, then synchronizing on individual txns. */ long firstActive = DbLsn.NULL_LSN; allTxnLatch.acquire(); try { Iterator iter = allTxns.iterator(); while(iter.hasNext()) { long txnFirstActive = ((Txn) iter.next()).getFirstActiveLsn(); if (firstActive == DbLsn.NULL_LSN) { firstActive = txnFirstActive; } else if (txnFirstActive != DbLsn.NULL_LSN) { if (DbLsn.compareTo(txnFirstActive, firstActive) < 0) { firstActive = txnFirstActive; } } } } finally { allTxnLatch.release(); } return firstActive; } /* * Statistics */ /** * Collect transaction related stats. */ public TransactionStats txnStat(StatsConfig config) throws DatabaseException { TransactionStats stats = new TransactionStats(); allTxnLatch.acquire(); try { stats.setNCommits(numCommits); stats.setNAborts(numAborts); stats.setNXAPrepares(numXAPrepares); stats.setNXACommits(numXACommits); stats.setNXAAborts(numXAAborts); stats.setNActive(allTxns.size()); TransactionStats.Active[] activeSet = new TransactionStats.Active[stats.getNActive()]; stats.setActiveTxns(activeSet); Iterator iter = allTxns.iterator(); int i = 0; while (iter.hasNext()) { Locker txn = (Locker) iter.next(); activeSet[i] = new TransactionStats.Active (txn.toString(), txn.getId(), 0); i++; } if (config.getClear()) { numCommits = 0; numAborts = 0; numXACommits = 0; numXAAborts = 0; } } finally { allTxnLatch.release(); } return stats; } /** * Collect lock related stats. */ public LockStats lockStat(StatsConfig config) throws DatabaseException { return lockManager.lockStat(config); } }